const http = require('http');
const fs   = require('fs');
const path = require('path');

const {
  NAV_TEMPLATES,
  CLIP_TEMPLATES,
  EDIT_TEMPLATES,
  COMPOSITION_RULES
} = require('./tasks');

const MIME = {
  '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript',
  '.json': 'application/json', '.png': 'image/png', '.gif': 'image/gif',
  '.svg': 'image/svg+xml', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg'
};

// ---------- 读取并索引 ex3.html（构造真实行号锚点） ----------
const EX_FILE = path.join(__dirname, 'ex3.html');
const RAW = fs.readFileSync(EX_FILE, 'utf8'); // ex3.html 源文本（见上传文件）
// 说明：ex3.html 内含多个章节标题与错词、段落编号等可预测文本。
// 参考你上传的页面结构（如 <h1>Welcom to Vim</h1>、“Instalation”段、20段 Paragraph 等）。:contentReference[oaicite:1]{index=1}

const LINES = RAW.split(/\r?\n/);
const LINE_COUNT = LINES.length;

// 简单工具
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
const randOf = (arr) => arr[Math.floor(Math.random() * arr.length)];
const randint = (a, b) => a + Math.floor(Math.random() * (b - a + 1));

// 构建若干锚点索引，尽量依靠稳定文本
const anchors = {
  // 章节标题行号
  sections: {},          // { intro/install/motions/editing/search/macros/faq: lineNo }
  // 段落编号（macros 段里的 Paragraph NN）
  paragraphs: [],        // [{num, line}]
  // 各类错词的出现行（只抓部分典型，够生成题即可）
  typos: {               // { token: [line1, line2, ...] }
    Welcom: [],
    editr: [],
    powrful: [],
    practis: [],
    navgation: [],
    'VIM': [],          // 用于大小写规范（非代码块）
    'vim': []
  },
  // 行尾有空格的行
  trailing: []
};

// 章节标题关键词 → 键名
const sectionMap = [
  { key: 'intro',   marker: '<section id="intro">' },
  { key: 'install', marker: '<section id="install">' },
  { key: 'motions', marker: '<section id="motions">' },
  { key: 'editing', marker: '<section id="editing">' },
  { key: 'search',  marker: '<section id="search">' },
  { key: 'macros',  marker: '<section id="macros">' },
  { key: 'faq',     marker: '<section id="faq">' }
];

// 扫描行，建立索引
LINES.forEach((line, i) => {
  const ln = i + 1;

  // 章节起始行号
  for (const { key, marker } of sectionMap) {
    if (line.includes(marker) && anchors.sections[key] == null) {
      anchors.sections[key] = ln;
    }
  }

  // 段落编号（macros 段落中的 “Paragraph NN:”）
  const paraMatch = line.match(/Paragraph\s+(\d+):/);
  if (paraMatch) {
    anchors.paragraphs.push({ num: parseInt(paraMatch[1], 10), line: ln });
  }

  // 常见错词——只在非代码块的普通行抓取（粗略过滤）
  if (!line.includes('<code>') && !line.includes('</code>') && !line.startsWith('```')) {
    for (const token of Object.keys(anchors.typos)) {
      if (line.includes(token)) anchors.typos[token].push(ln);
    }
  }

  // 行尾空格
  if (/\s+$/.test(line) && !/^\s*$/.test(line)) {
    anchors.trailing.push(ln);
  }
});

// 若某些锚点缺失，做兜底
for (const { key } of sectionMap) {
  if (!anchors.sections[key]) anchors.sections[key] = 1;
}
if (anchors.paragraphs.length === 0) anchors.paragraphs.push({ num: 1, line: 1 });

// ---------- 题目生成（按类别模板 + 锚点填充） ----------
function fillTemplate(t) {
  // 复制一份文本
  let s = t.text;

  // 可用候选
  const anyLine = randint(1, LINE_COUNT);
  const secKeys = Object.keys(anchors.sections);
  const secKey = randOf(secKeys);
  const secNameMap = { intro:'“Intro”', install:'“Installation”', motions:'“Motions”',
    editing:'“Editing”', search:'“Search & Replace”', macros:'“Macros”', faq:'“FAQ”' };
  const secName = secNameMap[secKey] || '相应';

  // 常用替换对
  const pairs = [
    { from:'Welcom', to:'Welcome', pool: anchors.typos.Welcom },
    { from:'editr',  to:'editor',  pool: anchors.typos.editr  },
    { from:'powrful',to:'powerful',pool: anchors.typos.powrful},
    { from:'practis',to:'practice',pool: anchors.typos.practis},
    { from:'navgation',to:'navigation',pool: anchors.typos.navgation}
  ];
  const pair = randOf(pairs);
  const fromLine = pair.pool.length ? randOf(pair.pool) : anyLine;

  // 生成范围
  const L1 = randint(1, LINE_COUNT);
  const L2 = clamp(L1 + randint(1, 5), 1, LINE_COUNT);
  const TO = randint(1, LINE_COUNT);

  // pattern 选择一个常见错词用来 / 搜索
  const anyPatternKey = randOf(Object.keys(anchors.typos));
  const pattern = anyPatternKey;

  // 占位符替换
  s = s.replaceAll('{line}', String(anyLine))
       .replaceAll('{fromLine}', String(fromLine))
       .replaceAll('{toLine}', String(TO))
       .replaceAll('{startLine}', String(Math.min(L1, L2)))
       .replaceAll('{endLine}', String(Math.max(L1, L2)))
       .replaceAll('{pattern}', pattern)
       .replaceAll('{from}', pair.from)
       .replaceAll('{to}', pair.to)
       .replaceAll('{sectionName}', secName);

  return { category: t.category, text: s };
}

function composeTasksByRarity(rarity) {
  const rule = COMPOSITION_RULES[rarity];
  const pools = {
    NAV: [...NAV_TEMPLATES],
    CLIP:[...CLIP_TEMPLATES],
    EDIT:[...EDIT_TEMPLATES]
  };

  const selected = [];
  const categories = ['NAV','CLIP','EDIT'];

  // 如果规则里指定了每类最小题数，先从每类中抽取所需数量（不重复同一模板）
  if (rule.minPerCategory) {
    for (const cat of categories) {
      const need = rule.minPerCategory[cat] || 0;
      for (let i = 0; i < need; i++) {
        const pool = pools[cat];
        if (!pool || pool.length === 0) break;
        const idx = Math.floor(Math.random() * pool.length);
        const tpl = pool.splice(idx, 1)[0];
        selected.push(fillTemplate(tpl));
      }
    }
  } else {
    // 兼容旧规则：保证覆盖 requireCategories 个类别
    const needCover = Math.min(rule.requireCategories, categories.length);
    const coverCats = categories.slice().sort(() => Math.random() - 0.5).slice(0, needCover);
    for (const cat of coverCats) {
      const tpl = randOf(pools[cat]);
      selected.push(fillTemplate(tpl));
    }
  }

  // 补满步数（从剩余 pool 中随机挑选，避免重复同一模板）
  while (selected.length < rule.steps) {
    // 随机选类，如果该类没剩余模板则选其他非空类
    let cat = randOf(categories);
    if (!pools[cat] || pools[cat].length === 0) {
      const nonEmpty = categories.filter(c => pools[c] && pools[c].length > 0);
      if (nonEmpty.length === 0) break; // 无可用模板
      cat = randOf(nonEmpty);
    }
    const pool = pools[cat];
    const idx = Math.floor(Math.random() * pool.length);
    const tpl = pool.splice(idx, 1)[0];
    selected.push(fillTemplate(tpl));
  }

  return selected;
}

// ---------- 抽卡概率 ----------
function pickRarity() {
  const r = Math.random(); // SSR 10%、SR 40%、R 50%
  if (r < 0.10) return 'SSR';
  if (r < 0.50) return 'SR';
  return 'R';
}

// ---------- HTTP ----------
const server = http.createServer((req, res) => {
  const url = decodeURIComponent(req.url.split('?')[0]);

  if (url === '/draw') {
    const rarity = pickRarity();
    const tasks = composeTasksByRarity(rarity);

    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ rarity, tasks, anchorsPreview: {
      sections: anchors.sections,
      paragraphCount: anchors.paragraphs.length
    }}));
    return;
  }

  if (req.method !== 'GET') {
    res.writeHead(405, { 'Content-Type': 'text/plain' });
    res.end('Method Not Allowed');
    return;
  }

  // 静态资源（路径消毒以防止路径遍历）
  const publicRoot = path.join(__dirname, 'public');
  // 规范化并去掉前导斜杠
  let rel = url.replace(/^\//, '');
  if (!rel) rel = 'index.html';

  // 禁止包含上级目录引用
  if (rel.includes('..')) {
    res.writeHead(400, { 'Content-Type': 'text/plain' });
    res.end('Bad Request');
    return;
  }

  let filePath = path.join(publicRoot, rel);
  // 如果是目录，尝试加载 index.html
  try {
    const stat = fs.existsSync(filePath) && fs.statSync(filePath);
    if (stat && stat.isDirectory()) filePath = path.join(filePath, 'index.html');
  } catch (e) {
    // fallthrough to 404
  }

  // 最后确认文件仍在 publicRoot 下（防止通过符号链接绕过）
  const safe = path.resolve(filePath).startsWith(path.resolve(publicRoot) + path.sep) || path.resolve(filePath) === path.resolve(publicRoot);
  if (!safe) {
    res.writeHead(403, { 'Content-Type': 'text/plain' });
    res.end('Forbidden');
    return;
  }

  fs.readFile(filePath, (err, buf) => {
    if (err) {
      res.writeHead(404, { 'Content-Type': 'text/plain' });
      res.end('404 Not Found');
      return;
    }
    const ext = path.extname(filePath).toLowerCase();
    res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
    res.end(buf);
  });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log('Server on http://localhost:' + PORT);
});
